﻿/* 
 * BtCommLib - the OpenWebNet .NET Library
 *
 * Copyright (C) 2011 BTicino S.p.A.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using BTicino.BtCommLib.Attributes;
using BTicino.BtCommLib.Exceptions;
using BTicino.BtCommLib.Domain.LowLevel;
using BTicino.BtCommLib.Trace;
using BTicino.BtCommLib.Mappers;

namespace BTicino.BtCommLib.Domain.Util
{
    public class BTOpenConverter
    {

        #region Data members

        private static BTLibLogger logger = BTLibLogger.createLogger("BTOpenConverter");

        /// <summary>
        /// search filter used by the reflection: non-static attributes, public and private
        /// </summary>
        private const BindingFlags FilteringBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        /// <summary>
        /// returns a list of attributes of a derived class in an inheritance hierarchy
        /// </summary>
        /// <param name="derivedType">Class Type</param>
        /// <returns>returns a list of attributes of a derived class in an inheritance hierarchy, exception otherwise</returns>
        public static List<FieldInfo> ListFieldsOfHierarchy
            (
            Type derivedType    // tipo della classe derivata
            )
        {

            Dictionary<string, FieldInfo> retVal = new Dictionary<string, FieldInfo>();


            while (derivedType != typeof(object))
            {
                
                foreach (FieldInfo f in derivedType.GetFields(FilteringBindings))
                {
                    if (!retVal.ContainsKey(f.Name))
                    {
                        retVal.Add(f.Name, f);
                    }
                }
                derivedType = derivedType.BaseType;
            }

            
            return new List<FieldInfo>(retVal.Values);
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Get Right Type of Low level Msg with pre-compiled fields
        /// </summary>
        /// <param name="type">Class Type</param>
        /// <returns>Return e Low level messagio (BTOpenMessage) starting from BTOpenMsgType class type, exception if not found</returns>
        private object GetLowMsg(string classType)
        {
            BTOpenMessage om = new BTOpenMessage();
            
            switch (classType)
            {
                case BTOpenMsgType.Request:
                    om.MsgType = BTOpenMsgType.Request;
                    break;
                case BTOpenMsgType.RequestStatus:
                    om.MsgType = BTOpenMsgType.RequestStatus;
                    break;
                case BTOpenMsgType.RequestDimension:
                    om.MsgType = BTOpenMsgType.RequestDimension;
                    break;
                case BTOpenMsgType.RequestWriteDimension:
                    om.MsgType = BTOpenMsgType.RequestWriteDimension;
                    break;
                default:
                    throw new LibException("Message Type not found!");
            }

            return om;

        }

        #endregion

        #region Public methods

        /// <summary>
        /// Convert from High level language message, to Low level language message (BTOpenMessage)
        /// This process is enabled through the use of reflection
        /// </summary>
        /// <param name="theHighMsg">Frame to be sent, write in high level object</param>
        /// <returns>Object converted, excption if some errers are occurred</returns>
        public object ConvertToLowMsg(object theHighMsg)
        {

            
            Type pType = theHighMsg.GetType();
            HighLevelCtxAttr highCtxAttr = new HighLevelCtxAttr(theHighMsg);

            MsgType[] pMsgTypeArray = (MsgType[])pType.GetCustomAttributes(typeof(MsgType), false);
            if (pMsgTypeArray.Length <= 0)
            {
                throw new LibException("Class \"" + pType.Name + "\" does not contain MsgType[...] attribute");
            }

            string classMsgType = pMsgTypeArray[0].TypeCode;
            if (classMsgType == null)
            {
                throw new LibException("MsgType Attribute has not a correct value");
            }

            logger.Debug("[MsgType] TypeName=" + classMsgType);
            
            
            //Open Message
            BTOpenMessage oMsg = (BTOpenMessage)GetLowMsg(classMsgType);


            
            foreach (
                FieldInfo field in ListFieldsOfHierarchy(pType))
            {
                string resValue = null;
                string[] resValueArray = null;

                //Get Field Value
                object val = field.GetValue(theHighMsg);
                if (val == null)
                {
                    logger.Debug("Field " + field.Name + " is null, not processed...");
                    continue;
                }


                Mapper[] pMapperArray = (Mapper[])field.GetCustomAttributes(typeof(Mapper), false);


                if (pMapperArray.Length > 0)
                {
                    //Get Attribute
                    Type classMapper = pMapperArray[0].MapperClass;
                    if (classMapper == null)
                    {
                        throw new LibException("Mapper Attribute has not a correct value");
                    }

                    try
                    {
                        // Create Instance of Target Class
                        object targetInstance = Activator.CreateInstance(classMapper);

                        if (val.GetType().IsArray)
                        {
                            //Invoke target (Array) method and get result
                            resValueArray = (string[])classMapper.GetMethod("GetValuesToLowLevelArray").Invoke(targetInstance, new Object[] { val, highCtxAttr });

                            if (resValueArray == null)
                            {
                                throw new LibException("Field " + field.Name + " taken from " + classMapper.Name + " is null");
                            }
                        }
                        else
                        {
                            //Invoke target method and get result
                            resValue = (string)classMapper.GetMethod("GetValueToLowLevel").Invoke(targetInstance, new Object[] { val, highCtxAttr });

                            if (resValue == null)
                            {
                                throw new LibException("Field " + field.Name + " taken from " + classMapper.Name + " is null");
                            }
                        }


                        logger.Debug("[MAPPER] ClassName=" + classMapper.Name);
                        logger.Debug("Field Value=" + resValue);
                    }
                    catch (LibException)
                    {
                        throw;
                    }
                    catch (System.Exception ex)
                    {

                        logger.Error("Error - couldn't obtain method GetValue from " + classMapper.Name);
                        logger.Error("EXCEPTION OUTPUT " + ex.Message + " " + ex.InnerException);

                        throw new LibException("Conversion Method ", ex);
                    }

                }
                //NO Mapper Found
                else 
                {
                    if (val.GetType().IsArray)
                        resValueArray = (string[])val;
                    else 
                        resValue = (string)val;
                }


                Who[] pWhoArray = (Who[])field.GetCustomAttributes(typeof(Who), false);
                if (pWhoArray.Length > 0)
                {
                    logger.Debug("[WHO]");
                    oMsg.Who = resValue;
                    continue;
                }

                What[] pWhatArray = (What[])field.GetCustomAttributes(typeof(What), false);
                if (pWhatArray.Length > 0)
                {
                    logger.Debug("[What]");
                    oMsg.What = resValue;
                    continue;
                }

                Where[] pWhereArray = (Where[])field.GetCustomAttributes(typeof(Where), false);
                if (pWhereArray.Length > 0)
                {
                    logger.Debug("[Where]");
                    oMsg.Where = resValue;
                    continue;
                }

                Dimension[] pDimensionArray = (Dimension[])field.GetCustomAttributes(typeof(Dimension), false);
                if (pDimensionArray.Length > 0)
                {
                    logger.Debug("[Dimension]");
                    oMsg.Dimension = resValue;
                    continue;
                }

                DimValues[] pDimValuesArray = (DimValues[])field.GetCustomAttributes(typeof(DimValues), false);
                if (pDimValuesArray.Length > 0)
                {
                    logger.Debug("[DimValues]");
                    oMsg.DimValues = resValueArray;
                    continue;
                }

                Raw[] pRawArray = (Raw[])field.GetCustomAttributes(typeof(Raw), false);
                if (pRawArray.Length > 0)
                {
                    logger.Debug("[Raw]");
                    logger.Debug("Raw Value=" + resValue);
                    oMsg.OpenRawMsg = resValue;
                    continue;
                }

            }

            return oMsg;
        }

        /// <summary>
        /// Convertion from Low level language message (BTOpenmessage), high level language message
        /// This process is enabled through the use of reflection
        /// </summary>
        /// <param name="openMsg">BTicino OPEN frame, translat in BTOpenMessage</param>
        /// <param name="highMsg">returned object</param>
        public void ConvertToHighMsg(BTOpenMessage openMsg, object highMsg)
        {
            
 
            Type pType = highMsg.GetType();
            LowLevelCtxAttr lowCtxAttr = new LowLevelCtxAttr(openMsg);

            

            foreach (FieldInfo field in ListFieldsOfHierarchy(pType))
            {
                object curValue = null;

                Who[] pWhoArray = (Who[])field.GetCustomAttributes(typeof(Who), false);
                if (pWhoArray.Length > 0)
                {
                    curValue = openMsg.Who;
                    logger.Debug("[WHO]");
                }

                What[] pWhatArray = (What[])field.GetCustomAttributes(typeof(What), false);
                if (pWhatArray.Length > 0)
                {
                    curValue = openMsg.What;
                    logger.Debug("[What]");
                }

                Where[] pWhereArray = (Where[])field.GetCustomAttributes(typeof(Where), false);
                if (pWhereArray.Length > 0)
                {
                    curValue = openMsg.Where;
                    logger.Debug("[Where]");
                }

                Dimension[] pDimensionArray = (Dimension[])field.GetCustomAttributes(typeof(Dimension), false);
                if (pDimensionArray.Length > 0)
                {
                    curValue = openMsg.Dimension;
                    logger.Debug("[Dimension]");
                }

                DimValues[] pDimValuesArray = (DimValues[])field.GetCustomAttributes(typeof(DimValues), false);
                if (pDimValuesArray.Length > 0)
                {
                    curValue = openMsg.DimValues;
                    logger.Debug("[DimValues], curValue: " + curValue.GetType().IsArray); 
                }

                Raw[] pRawArray = (Raw[])field.GetCustomAttributes(typeof(Raw), false);
                if (pRawArray.Length > 0)
                {
                    curValue = openMsg.OpenRawMsg;
                    logger.Debug("[Raw]");
                    logger.Debug("Raw Value=" + curValue);
                }
                

                //Check if current value is null
                if (curValue == null)
                {
                    throw new LibException("Field " + field.Name + " is null");
                }

                //Check for Mapper Value
                object valueMapper = null;

                Mapper[] pMapperArray = (Mapper[])field.GetCustomAttributes(typeof(Mapper), false);
                if (pMapperArray.Length > 0)
                {
                    //Get Attribute
                    Type classMapper = pMapperArray[0].MapperClass;
                    if (classMapper == null)
                    {
                        throw new LibException("Mapper Attribute has not a correct value");
                    }

                    try
                    {
                        // Create Instance of Target Class
                        object targetInstance = Activator.CreateInstance(classMapper);

                        if (curValue.GetType().IsArray)
                            //Invoke target (Array) method and get result
                            valueMapper = classMapper.GetMethod("GetValuesToHighLevelArray").Invoke(targetInstance, new Object[] { curValue, lowCtxAttr });
                        else
                            //Invoke target method and get result
                            valueMapper = classMapper.GetMethod("GetValueToHighLevel").Invoke(targetInstance, new Object[] { curValue, lowCtxAttr });

                        if (valueMapper == null)
                        {
                            throw new LibException("Field " + field.Name + " taken from " + classMapper.Name + " is null");
                        }

                        field.SetValue(highMsg, valueMapper);

                        logger.Debug("[MAPPER] ClassName=" + classMapper.Name);
                        if (valueMapper.GetType().IsArray)
                        {
                            string debug = string.Empty;
                            foreach (string val in (string[])valueMapper){
                                debug += val;
                            }
                            logger.Debug("Field Value=" + debug);
                        }
                        else
                            logger.Debug("Field Value=" + valueMapper);
                    }
                    catch (LibException)
                    {
                        throw;
                    }
                    catch (System.Exception ex)
                    {

                        logger.Error("Error - couldn't obtain method GetValue from " + classMapper.Name);
                        logger.Error("EXCEPTION OUTPUT " + ex.Message + " " + ex.InnerException);

                        throw new LibException("Conversion Method ", ex);
                    }

                }
                //NO Mapper Found
                else
                {
                    field.SetValue(highMsg, curValue);
                }
                
            }

        }

        #endregion
    }
}
